﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Odyssey.Classes;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

namespace Odyssey.Controls
{
    /// <summary>
    /// Page animation with turnstile effect on a ListBox.
    /// </summary>
    public class TurnstileAnimation : SwingAnimation
    {
        private List<ListBoxItem> animatedItems;

        /// <summary>
        /// Gets or sets the delay between each item animation.
        /// </summary>
        public TimeSpan ItemDelay
        {
            get { return (TimeSpan)GetValue(ItemDelayProperty); }
            set { SetValue(ItemDelayProperty, value); }
        }

        public static readonly DependencyProperty ItemDelayProperty =
            DependencyProperty.Register("ItemDelay", typeof(TimeSpan), typeof(TurnstileAnimation), new PropertyMetadata(TimeSpan.FromMilliseconds(40d)));



        /// <summary>
        /// Gets or sets the listbox for which to apply turnstile animation. If no ListBox is specified, 
        /// the listbox is automatically determined as the first hierarchical listbox in the visual tree.
        /// </summary>
        public ListBox ListBox
        {
            get { return (ListBox)GetValue(ListBoxProperty); }
            set { SetValue(ListBoxProperty, value); }
        }


        /// <summary>
        /// Gets or sets the listbox for which to apply turnstile animation.
        /// </summary>
        public static readonly DependencyProperty ListBoxProperty =
            DependencyProperty.Register("ListBox", typeof(ListBox), typeof(TurnstileAnimation), new PropertyMetadata(null));


        protected override void InitializeShowStoryboard(Page page, Storyboard sb, bool preload)
        {
            //base.InitializeShowStoryboard(page, sb, preload);
            sb.Children.Clear();
            if (!preload) InitItems(page, sb, EasingMode.EaseInOut, Angle, 0d, ItemDelay);
            InitFrameAnimation(page, sb, EasingMode.EaseInOut, Angle, 0d, TimeSpan.FromMilliseconds(0), Duration);
            if (preload) sb.Completed += (s, e) => ResetItems();
        }


        protected override void InitializeHideStoryboard(Page page, Storyboard sb)
        {
         //   base.InitializeHideStoryboard(page, sb);
            var time = InitItems(page, sb, EasingMode.EaseInOut, null, Angle, TimeSpan.FromMilliseconds(0));
            InitFrameAnimation(page, sb, EasingMode.EaseInOut, null, Angle, time, Duration);
            sb.Completed += (s, e) => ResetItems();

        }

        private FrameworkElement GetViewPort()
        {
            ScrollViewer scrollViewer = new ItemsControlHelper(ListBox).ScrollHost;
            if (scrollViewer == null) return null;
            ScrollContentPresenter scp = scrollViewer == null ? null : scrollViewer.GetVisualDescendants().OfType<ScrollContentPresenter>().FirstOrDefault();
            return scp != null ? (FrameworkElement)scp : (FrameworkElement)scrollViewer;
        }

        private TimeSpan InitItems(Page page, Storyboard sb, EasingMode easingMode, double? from, double? to, TimeSpan beginTime)
        {
            sb.Children.Clear();
            Duration duration = this.Duration;

            if (ListBox == null) ListBox = page.GetVisualDescendants().OfType<ListBox>().FirstOrDefault();
            if (ListBox != null)
            {
                ListBox.UpdateLayout();
                ListBoxItem selectedItem = GetSelectedItem(ListBox);
                ListBox.SelectedItem = null;
                FrameworkElement viewport = GetViewPort();
                var items = ListBox.GetVisualDescendants()
                    .OfType<ListBoxItem>()
                    .SkipWhile(item => !IsItemVisible(item, viewport))
                    .TakeWhile(item => IsItemVisible(item, viewport))
                    .ToList();

                if (selectedItem != null)
                {
                    items.Remove(selectedItem);
                    items.Add(selectedItem);
                }
                animatedItems = items;
                TimeSpan itemDelay = ItemDelay;
                FrameworkElement rootElement = page.GetVisualChild(0);
                foreach (var item in items)
                {
                    GeneralTransform gt = item.TransformToVisual(rootElement);
                    Point globalCoords = gt.Transform(new Point(0, 0));
                    double heightAdjustment = item.Content is FrameworkElement ? ((item.Content as FrameworkElement).ActualHeight / 2) : (item.ActualHeight / 2);
                    double yCoord = globalCoords.Y + heightAdjustment;

                    double globalOffsetY = (rootElement.ActualHeight / 2) - yCoord;


                    PlaneProjection plane = new PlaneProjection { CenterOfRotationX = 0d, RotationY = from ?? 0d, GlobalOffsetY = -globalOffsetY };
                    item.Projection = plane;
                    DoubleAnimation da = new DoubleAnimation
                    {
                        BeginTime = beginTime,
                        Duration = duration,
                        To = to,
                        From = from,
                        EasingFunction = new CircleEase { EasingMode = easingMode }
                    };

                    CompositeTransform ct = new CompositeTransform();
                    ct.TranslateY = globalOffsetY;
                    item.RenderTransform = ct;



                    beginTime = beginTime.Add(itemDelay);
                    Storyboard.SetTarget(da, item);
                    Storyboard.SetTargetProperty(da, new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationY)"));
                    sb.Children.Add(da);
                }
            }

            return beginTime;
        }

        private static void InitFrameAnimation(Page page, Storyboard sb, EasingMode easingMode, double? from, double? to, TimeSpan beginTime, Duration duration)
        {
            DoubleAnimation tileRotation = new DoubleAnimation
            {
                To = to,
                From = from,
                BeginTime = beginTime,
                Duration = duration,
                EasingFunction = new CircleEase { EasingMode = easingMode }
            };
            sb.Children.Add(tileRotation);
            Storyboard.SetTarget(tileRotation, page);
            Storyboard.SetTargetProperty(tileRotation, new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationY)"));
        }

        private ListBoxItem GetSelectedItem(System.Windows.Controls.ListBox listBox)
        {
            int index = listBox.SelectedIndex;
            if (index < 0) return null;
            return listBox.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
        }


        private bool IsItemVisible(ListBoxItem item, FrameworkElement viewport)
        {

            Rect itemsHostRect = new Rect(0.0, 0.0, viewport.ActualWidth, viewport.ActualHeight);

            GeneralTransform transform = item.TransformToVisual(viewport);
            Rect listBoxItemRect = new Rect(transform.Transform(new Point()), transform.Transform(new Point(item.ActualWidth, item.ActualHeight)));

            const double threshold = 80;
            return ((listBoxItemRect.Bottom + threshold >= itemsHostRect.Top) && (listBoxItemRect.Top - threshold <= itemsHostRect.Bottom));
        }

        private void ResetItems()
        {
            if (animatedItems != null)
            {
                foreach (var item in animatedItems)
                {
                    item.Projection = null;
                    item.RenderTransform = null;
                }
                animatedItems = null;
            }
        }
    }
}
